﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
using UnityEditor;
#endif
using USClientKernel.Template;


namespace USClientKernel.Common.Utils
{
    public delegate void UpdateAssetBundleInfoLoadResult(float _progress, uint _loadedCount, bool _isDown, AssetBundle _ab);

    public class AssetBundleInfoLoadResult
    {
        private float m_progress;
        private bool m_isDown;
        private uint m_totalCount;
        private uint m_loadedCount;
        private AssetBundle m_assetBundle;

        public AssetBundleInfoLoadResult(uint _totalCount, out UpdateAssetBundleInfoLoadResult _ur)
        {
            m_progress = 0.0f;
            m_isDown = false;
            m_totalCount = _totalCount;
            m_loadedCount = 0;
            _ur = UpdateProperty;
        }

        private void UpdateProperty(float _progress, uint _loadedCount, bool _isDown, AssetBundle _ab)
        {
            m_progress = _progress;
            m_loadedCount = _loadedCount;
            m_isDown = _isDown;
            m_assetBundle = _ab;
        }

        public AssetBundle AssetBundle
        {
            // set { m_assetBundle = value; }
            get { return m_assetBundle; }
        }

        public float Progress
        {
            // set { m_progress = value; }
            get { return m_progress; }
        }

        public bool IsDown
        {
            // set { m_isDown = value; }
            get { return m_isDown; }
        }

        public uint TotalCount
        {
            get { return m_totalCount; }
        }

        public uint LoadedCount
        {
            // set { m_loadedCount = value; }
            get { return m_loadedCount; }
        }

    }

    public class AssetBundleInfo
    {
        public AssetBundle m_assetBundle;
        private bool m_bIsLoading;
        public string Path { get; private set; }
        public int RefCount { get; set; }
        private string[] m_arrDependencies;

        public AssetBundleInfo(string _path)
        {
            Path = _path;
        }

        public AssetBundleInfo(string _path, string[] _dependencies)
        {
            Path = _path;
            m_arrDependencies = _dependencies;
        }

        // 是否有效
        public bool IsValid
        {
            get { return null != m_assetBundle; } 
        }

        // 是否正在加载
        public bool IsLoading
        {
            get { return m_bIsLoading; }
        }

        // 是否加载完成
        public bool IsDown
        {
            get { return (null != m_assetBundle && !m_bIsLoading); }
        }

        // 同步加载
        public AssetBundle AssetBundle
        {
            get
            {
#if UNITY_EDITOR
                if (!IsValid)
                    _SearchAssetBundle();
#endif
                if (!IsValid && !IsLoading)
                    _AssetBundleLoad();
                return m_assetBundle;
            }
        }


        /// <summary>
        /// AssetBundle协程加载
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        public IEnumerator LoadCoroutine(Action<AssetBundle> _loaded)
        {
            if (!IsValid && !IsLoading)
            {
                _AssetBundleLoad();
                yield return null;
            }

            if (null != _loaded) _loaded(m_assetBundle);
            yield return null;
        }


        /// <summary>
        /// 在已经加载的池中寻找
        /// </summary>
        private AssetBundle _SearchAssetBundle()
        {
            AssetBundle ab = null;





            return ab;
        }


        /// <summary>
        /// AssetBundle加载
        /// </summary>
        private AssetBundle _AssetBundleLoad()
        {
            try
            {
                m_bIsLoading = true;
                _AssetBundleDependenciesLoad();
                m_assetBundle = AssetBundle.LoadFromFile(Path);
                m_bIsLoading = false;
                Debug.Log("AssetBundle Load Asset Package Path :" + Path);
            }
            catch (Exception e)
            {
                Debug.Log(e.ToString());
                throw e;
            }
            return null;
        }


        /// <summary>
        /// AssetBundle的所有依赖项加载
        /// </summary>
        private void _AssetBundleDependenciesLoad()
        {
            if (null == m_arrDependencies) return;
            foreach (string dependency in m_arrDependencies)
            {
                AssetBundleInfo abi = AssetsBundleManager.Instance.GetAssetBundleInfo(dependency);
                if (null == abi.AssetBundle)
                    throw new Exception("Failed to load AssetBundle dependency --> " + dependency);
            }
        }


        /// <summary>
        /// 异步加载AssetBundle
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        public IEnumerator LoadAsync(Action<AssetBundle> _loaded)
        {
            return LoadAsync(_loaded, null);
        }


        /// <summary>
        /// 异步加载AssetBundle
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        /// <param name="_progress">进度回调函数</param>
        public IEnumerator LoadAsync(Action<AssetBundle> _loaded, Action<AssetBundleCreateRequest> _progress)
        {
            //have load
            if (null != m_assetBundle)
            {
                _loaded(m_assetBundle);
                yield break;
            }

            //load AssetBundle
            m_bIsLoading = true;
            AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Path);

            while (!abcr.isDone)
            {
                if (abcr.progress >= 0.9f)
                    abcr.allowSceneActivation = true;
                if (null != _progress)
                    _progress(abcr);
                yield return null;
            }

            if (null != _progress)
                _progress(abcr);

            // 需要检查_Object 是否为null;
            m_assetBundle = abcr.assetBundle;
            m_bIsLoading = false;
            Debug.Log("AssetBundle Async Load Asset Package Path :" + Path);

            if (null != _loaded)
                _loaded(m_assetBundle);

            yield return abcr;
        }


        /// <summary>
        /// 异步加载AssetBundle,会检查所有依赖项，没有加载的依赖项进行加载。
        /// </summary>
        /// <param name="_loaded">完成回调函数</param>
        /// <param name="_progress">进度回调函数</param>
        public IEnumerator LoadAsyncEx(Action<AssetBundle> _loaded, Action<AssetBundleInfoLoadResult> _progress)
        {
            UpdateAssetBundleInfoLoadResult update;
            AssetBundleCreateRequest[] abcrArr = new AssetBundleCreateRequest[m_arrDependencies.Length];
            AssetBundleInfoLoadResult abilr = new AssetBundleInfoLoadResult((uint)m_arrDependencies.Length, out update);
            m_bIsLoading = true;
            float fTotalLoad = 1.0f;
            for (int i = 0; i < m_arrDependencies.Length; i++)
            {
                string dependency = m_arrDependencies[i];
                AssetBundleInfo abi = AssetsBundleManager.Instance.GetAssetBundleInfo(dependency);
                if (abi.IsValid)
                {
                    abcrArr[i] = null;
                }
                else
                {
                    abcrArr[i] = AssetBundle.LoadFromFileAsync(dependency);
                    fTotalLoad = 1.0f;
                }
                yield return null;
            }

            uint uiLoadCnt = 0;
            while (uiLoadCnt < m_arrDependencies.Length)
            {
                uiLoadCnt = 0;
                float fCurrLoad = 0.0f;
                for (int i = 0; i < m_arrDependencies.Length; i++)
                {
                    AssetBundleCreateRequest _abcr = abcrArr[i];
                    if (null != _abcr)
                    {
                        if (_abcr.progress >= 0.9f)
                            _abcr.allowSceneActivation = true;
                        if (_abcr.isDone)
                            uiLoadCnt += 1;
                        fCurrLoad += _abcr.progress;
                    }
                    else
                    {
                        uiLoadCnt += 1;
                    }
                }
                update(fCurrLoad / fTotalLoad, uiLoadCnt, false, null);
                if (null != _progress)
                    _progress(abilr);
                yield return null;
            }

            AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Path);
            while (!abcr.isDone)
            {
                float fCurrLoad = fTotalLoad - 1.0f;
                if (abcr.progress >= 0.9f)
                    abcr.allowSceneActivation = true;
                if (abcr.isDone)
                    uiLoadCnt += 1;
                fCurrLoad += abcr.progress;
                update(fCurrLoad / fTotalLoad, uiLoadCnt, false, null);
                if (null != _progress)
                    _progress(abilr);
                yield return null;
            }

            if (abcr.isDone && null != abcr.assetBundle)
            {
                m_bIsLoading = false;
                uiLoadCnt = abilr.TotalCount;
                update(1.0f, uiLoadCnt, true, abcr.assetBundle);

                if (null != _progress)
                    _progress(abilr);
                if (null != _loaded)
                    _loaded(abilr.AssetBundle);
            }
            else
            {
                m_bIsLoading = false;
                throw new Exception("Failed to load assetBundle Path --> " + Path);
            }

            yield return null;
        }
}

    public class AssetsBundleManager : Singleton<AssetsBundleManager>
    {
        private static string ROOT_ASSET_BUNDLE_NAME;
        private Dictionary<string, AssetBundleInfo> m_cacheAssetsBundle;
        private Dictionary<string, AssetBundleInfo> m_localAssetsBundle;
        private AssetBundle m_rootAssetBundle;
        private AssetBundleManifest m_rootManifest;

        public static void SetRootAssetBundle(string name)
        {
            ROOT_ASSET_BUNDLE_NAME = name;
        }

        protected override void Init()
        {
            AssetBundle.UnloadAllAssetBundles(true);
            m_cacheAssetsBundle = new Dictionary<string, AssetBundleInfo>();
            m_localAssetsBundle = new Dictionary<string, AssetBundleInfo>();
            m_rootAssetBundle = GetAssetBundleInfoByPath(ROOT_ASSET_BUNDLE_NAME).AssetBundle;
            m_rootManifest = m_rootAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            Debug.Log("AssetsBundleManager Init");
        }


        #region GetAssetBundleInfo

        private AssetBundleInfo GetAssetBundleInfoByPath(string _path)
        {
            return GetAssetBundleInfoByPath(_path, null);
        }

        private AssetBundleInfo GetAssetBundleInfoByPath(string _path, Action<AssetBundle> _loaded)
        {
            return GetAssetBundleInfo(_GenerateAssetBundleName(_path), _loaded);
        }

        public AssetBundleInfo GetAssetBundleInfo(string _assetBundleName)
        {
            return GetAssetBundleInfo(_assetBundleName, null);
        }

        public AssetBundleInfo GetAssetBundleInfo(string _assetBundleName, Action<AssetBundle> _loaded)
        {
            AssetBundleInfo assetBundleInfo = null;
            if (m_cacheAssetsBundle.TryGetValue(_assetBundleName, out assetBundleInfo))
            {
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }

            string assetBundlePath = string.Concat(Application.persistentDataPath, "/", _assetBundleName);
            if (File.Exists(assetBundlePath))
            {
                assetBundleInfo = new AssetBundleInfo(assetBundlePath, GetAllDependencies(_assetBundleName));
                m_cacheAssetsBundle.Add(_assetBundleName, assetBundleInfo);
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }

            if (m_localAssetsBundle.TryGetValue(_assetBundleName, out assetBundleInfo))
            {
                assetBundleInfo.RefCount++;
                return assetBundleInfo;
            }

            assetBundlePath = string.Concat(Application.streamingAssetsPath, "/", _assetBundleName);
            assetBundleInfo = new AssetBundleInfo(assetBundlePath, GetAllDependencies(_assetBundleName));
            m_localAssetsBundle.Add(_assetBundleName, assetBundleInfo);
            assetBundleInfo.RefCount++;
            return assetBundleInfo;
        }

        public string[] GetAllDependencies(string name)
        {
            if (!m_rootManifest)
                return null;
            return m_rootManifest.GetAllDependencies(name);
        }

        private string _GenerateAssetBundleName(string _path)
        {
            string rt = _path.Replace(@"\\", "/").Replace(@"\", "/");
            string rp = "Assets/";
            int idx = rt.IndexOf(rp);
            if (idx > 0)
                rt = rt.Substring(idx + rp.Length);
            idx = rt.LastIndexOf("/");
            if (idx > 0)
                rt = rt.Substring(0, idx);

            rt = rt.Replace(@"/", "_");

            if (!rt.StartsWith(ROOT_ASSET_BUNDLE_NAME))
            {
                if (_path.EndsWith(".unity"))
                    rt = string.Concat(rt, ".u3d");
                else
                    rt = string.Concat(rt, ".pkg");
            }
            return rt;
        }

        #endregion


        #region Load AssetBundle

        public AssetBundle Load(string _path)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path);
            if (null != _assetBundleInfo)
                return _assetBundleInfo.AssetBundle;
            return null;
        }

        #endregion


        #region Load Coroutine AssetBundle

        public void LoadCoroutine(string _path, Action<AssetBundle> _loaded)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            if (null != _assetBundleInfo)
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadCoroutine(_loaded));
        }

        #endregion


        #region Load Async AssetBundle

        public void LoadAsync(string _path, Action<AssetBundle> _loaded)
        {
            LoadAsync(_path, _loaded, null);
        }

        public void LoadAsync(string _path, Action<AssetBundle> _loaded, Action<AssetBundleCreateRequest> _progress)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            if (null != _assetBundleInfo)
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadAsync(_loaded, _progress));
        }

        public void LoadAsyncEx(string _path, Action<AssetBundle> _loaded, Action<AssetBundleInfoLoadResult> _progress)
        {
            AssetBundleInfo _assetBundleInfo = GetAssetBundleInfoByPath(_path, _loaded);
            if (null != _assetBundleInfo)
                CoroutineController.Instance.StartCoroutine(_assetBundleInfo.LoadAsyncEx(_loaded, _progress));
        }

        #endregion


        #region Load Sync Asset

        public Object LoadAssetSync(string _path)
        {
            AssetBundle assetBundle = Load(_path);
            return assetBundle.LoadAsset(_path);
        }

        #endregion


        #region Load Coroutine AssetBundle

        public void LoadAssetCoroutine(string _path, Action<Object> _loaded)
        {
            LoadCoroutine(
                _path, 
                (AssetBundle __ab) => {
                    if (null != _loaded)
                        _loaded(__ab.LoadAsset(_path));
                }
            );
        }

        #endregion


        #region Load Async Asset

        private IEnumerator _LoadAssetAsyncHelper(string _path, Action<Object> _loaded, Action<float> _progress, AssetBundleInfoLoadResult _abilr = null)
        {
            AssetBundle ab = _abilr.AssetBundle;

            // Load Asset Async
            AssetBundleRequest abr = ab.LoadAssetAsync(_path);
            while (!abr.isDone)
            {
                if (abr.progress >= 0.9f)
                    abr.allowSceneActivation = true;
                if (null != _progress)
                {
                    float fTotal = 1;
                    float fCur = abr.progress;
                    if (null != _abilr)
                    {
                        fTotal += _abilr.TotalCount;
                        fCur += _abilr.TotalCount;
                    }
                    _progress(fCur/fTotal);
                }
                yield return null;
            }

            //float fTotal = 1;
            //if (null != _abilr)
            //fTotal = _abilr

            yield return null;
        }

        public void LoadAssetAsync(string _path, Action<Object> _loaded, Action<float> _progress)
        {
            LoadAsyncEx(
                _path,
                (AssetBundle __ab) => {
                    if (null != _loaded)
                        _loaded(__ab.LoadAsset(_path));
                },
                (AssetBundleInfoLoadResult __abilr) =>
                {
                    if (null != _progress)
                    {
                        float fTotal = __abilr.TotalCount + 1;
                        float fCur = __abilr.Progress * __abilr.TotalCount;
                        if (!__abilr.IsDown)
                            _progress(fCur / fTotal);
                        else
                            CoroutineController.Instance.StartCoroutine(_LoadAssetAsyncHelper(_path, _loaded, _progress, __abilr));
                    }
                }
            );
        }

        #endregion
    }
}


